#include "ColladaGeometry.h"

namespace lib3dw
{
	ColladaGeometry::ColladaGeometry(FCDocument* colladaDocument)
	{
		triangleCount = 0;
		translationVector = new Vector3();
		scaleVector = new Vector3(1.0, 1.0, 1.0);
		rotationVector = new Vector4();
		FCDGeometryLibrary* geometryLibrary = colladaDocument->GetGeometryLibrary();

		vboId = 0;
		switch(ConfigSingleton::Instance()->GetRenderMode())
		{
			case RenderVBO:
			{
				//retrieve the meshId to check if we already loaded this geometry data (meshId needs to be unique)
				std::string meshId = (std::string)geometryLibrary->GetEntity(0)->GetMesh()->GetDaeId();
				std::map<std::string, VBOInformation*>* loadedVBOs = VBOProxySingleton::Instance()->GetLoadedVBOs();
				VBOInformation* currentVBO = (*loadedVBOs)[meshId];
		
				if(currentVBO == NULL)
				{
					FCDGeometryLibrary* geometryLibrary = colladaDocument->GetGeometryLibrary();
					ProcessGeometryLibrary(geometryLibrary);
					InitVBO();
					(*loadedVBOs)[meshId] = new VBOInformation(meshId, vboId, vertexCount);
				}
				vboId = (*loadedVBOs)[meshId]->GetId();
				vertexCount = (*loadedVBOs)[meshId]->GetVertexCount();
				break;
			}
			case RenderDisplayList:
			{
				//fall through		
			}
			case RenderImmediate:
			{
				ProcessGeometryLibrary(geometryLibrary);
				BuildDisplayList();
				break;
			}
		}
	}
	
	ColladaGeometry::~ColladaGeometry()
	{		
		delete translationVector;
		delete scaleVector;
		delete rotationVector;
		if(ConfigSingleton::Instance()->GetRenderMode() == RenderImmediate) delete [] triangles;
	}

	const unsigned short ColladaGeometry::GetTriangleCount()
	{
		return triangleCount; 
	}

	Triangle* ColladaGeometry::GetTriangle(unsigned short index) const
	{
		if (index < triangleCount)
		{
			return &triangles[index];
		}
		else
		{
			return NULL;
		}
	}

	void ColladaGeometry::ProcessGeometryLibrary(FCDGeometryLibrary* library)
	{
		size_t geometryCount = library->GetEntityCount();
		for (size_t i = 0; i < geometryCount; ++i)
		{
			FCDGeometry* geometry = library->GetEntity(i);
			if (geometry->IsMesh())
			{
				ProcessMesh(geometry->GetMesh());
			}
		}
	}

	void ColladaGeometry::ProcessMesh(FCDGeometryMesh* mesh)
	{
		unsigned short polygonGroupCount = mesh->GetPolygonsCount();
		for (unsigned short polygonGroupIndex = 0; polygonGroupIndex < polygonGroupCount; polygonGroupIndex++)
		{
			FCDGeometryPolygons *polygonGroup = mesh->GetPolygons(polygonGroupIndex);

			if (polygonGroup->IsTriangles())
			{
				size_t faceCount = polygonGroup->GetFaceCount();
				triangleCount = faceCount;
				triangles = new Triangle[triangleCount];
				ProcessVertexPositions(polygonGroup);
				ProcessVertexNormals(polygonGroup);
				ProcessTextureCoordinates(polygonGroup);
			}
		}
	}

	void ColladaGeometry::BuildDisplayList()
	{
		glNewList(displayList, GL_COMPILE);	
		glBegin(GL_TRIANGLES);
		for(unsigned short triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++)
		{
			const Triangle *triangle = GetTriangle(triangleIndex);
			for (unsigned short i = 0; i < 3; i++)
			{
				Vertex* v = triangle->GetVertex(i);
				const Vector3* pos = v->GetPosition();
				const Vector3* normal = v->GetNormal();
				const Vector2* texCoord = v->GetTexCoord2D();
				glTexCoord2f(texCoord->x, texCoord->y);
				glNormal3f(normal->x, normal->y, normal->z);
				glVertex3f(pos->x, pos->y, pos->z);
			}
		}
		glEnd();
		glEndList();
	}
	
	void ColladaGeometry::InitVBO()
	{
			vertexCount = triangleCount * 3;
			glGenBuffersARB(1, &vboId);
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
			glBufferDataARB(GL_ARRAY_BUFFER_ARB, 0, NULL, GL_STATIC_DRAW_ARB);
			glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexCount * 3 * 3 * 2 * sizeof(float), NULL, GL_STATIC_DRAW_ARB);
			float* vertexPointer = (float*)glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);	
			
			for (unsigned int triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++)
			{
				for (unsigned int vertexIndex = 0; vertexIndex < 3; vertexIndex++)
				{
					// we need 8bytes to store texCoords, normals and positions in an interleaved array
					unsigned int offset = (triangleIndex * 3 + vertexIndex) * 8;
					Triangle* currentTriangle = GetTriangle(triangleIndex);
					Vertex* currentVertex = currentTriangle->GetVertex(vertexIndex);
					const Vector3* currentPosition = currentVertex->GetPosition();
					const Vector3* currentNormal = currentVertex->GetNormal();
					const Vector2* currentTexCoord2D = currentVertex->GetTexCoord2D();
					
					// we use GL_T2F_N3F_V3F as vertex format
					vertexPointer[offset+0] = currentTexCoord2D->x;
					vertexPointer[offset+1] = currentTexCoord2D->y;
					
					vertexPointer[offset+2] = currentNormal->x;
					vertexPointer[offset+3] = currentNormal->y;
					vertexPointer[offset+4] = currentNormal->z;
					
					vertexPointer[offset+5] = currentPosition->x;
					vertexPointer[offset+6] = currentPosition->y;
					vertexPointer[offset+7] = currentPosition->z;
				}
			}
			glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
			delete [] triangles;
	}

	void ColladaGeometry::ProcessVertexPositions(FCDGeometryPolygons* polygonGroup)
	{
		if(polygonGroup->FindInput(FUDaeGeometryInput::POSITION))
		{
			const FCDGeometryPolygonsInput* positionPolygonsInput = polygonGroup->FindInput(FUDaeGeometryInput::POSITION);
			const FCDGeometrySource* positionSource = positionPolygonsInput->GetSource();
			const FloatList sourceData = positionSource->GetSourceData();
			const unsigned int stride = positionSource->GetSourceStride();

			// Check if we have 3 dimensions
			if (stride >= 3)
			{
				const UInt32List *indexList = polygonGroup->FindIndices(positionPolygonsInput);
				unsigned int indexCount = indexList->size();
				for (unsigned int triangleIndex = 0; triangleIndex < this->GetTriangleCount(); triangleIndex++)
				{
					for (unsigned int index = 0; index < 3; index++)
					{
						unsigned int listIndex = (triangleIndex * 3) + index;
						unsigned int firstIndex = (indexList[0][listIndex]) * stride;
						if ((firstIndex + 2) < sourceData.size())
						{
							Triangle* t = this->GetTriangle(triangleIndex);
							Vertex* v = t->GetVertex(index);
							v->SetPosition(sourceData[firstIndex + 0], sourceData[firstIndex + 1], sourceData[firstIndex + 2]);
						}
					}
				}
			}
		}
	}
	
	void ColladaGeometry::ProcessVertexNormals(FCDGeometryPolygons* polygonGroup)
	{
		if(polygonGroup->FindInput(FUDaeGeometryInput::NORMAL))
		{
			const FCDGeometryPolygonsInput* normalPolygonsInput = polygonGroup->FindInput(FUDaeGeometryInput::NORMAL);
			const FCDGeometrySource* normalSource = normalPolygonsInput->GetSource();
			const FloatList sourceData = normalSource->GetSourceData();
			const unsigned int stride = normalSource->GetSourceStride();

			// Check if we have 3 dimensions
			if(stride >= 3)
			{
				const UInt32List *indexList = polygonGroup->FindIndices(normalPolygonsInput);
				unsigned int indexCount = indexList->size();
				for (unsigned int triangleIndex = 0; triangleIndex < this->GetTriangleCount(); triangleIndex++)
				{
					for (unsigned int index = 0; index < 3; index++)
					{
						unsigned int listIndex = (triangleIndex * 3) + index;
						unsigned int firstIndex = (indexList[0][listIndex]) * stride;
						if ((firstIndex + 2) < sourceData.size())
						{
							Triangle* t = this->GetTriangle(triangleIndex);
							Vertex* v = t->GetVertex(index);
							v->SetNormal(sourceData[firstIndex + 0], sourceData[firstIndex + 1], sourceData[firstIndex + 2]);
						}
					}
				}
			}
		}
	}
	
	void ColladaGeometry::ProcessTextureCoordinates(FCDGeometryPolygons* polygonGroup)
	{
		const FCDGeometryPolygonsInput *texCoordPolygonsInput = polygonGroup->FindInput(FUDaeGeometryInput::TEXCOORD);
		if(texCoordPolygonsInput)
		{
			const FCDGeometrySource *texCoordSource = texCoordPolygonsInput->GetSource();
			const FloatList sourceData = texCoordSource->GetSourceData();
			const unsigned int stride = texCoordSource->GetSourceStride();
			
			if (stride >= 2)
			{
				const UInt32List *indexList = polygonGroup->FindIndices(texCoordPolygonsInput);
				unsigned int indexCount = indexList->size();
				for (unsigned int triangleIndex = 0; triangleIndex < this->GetTriangleCount(); triangleIndex++)
				{
					for (unsigned int index = 0; index < 3; index++)
					{
						unsigned int inputIndex = (triangleIndex * 3) + index;
						unsigned int firstIndex = (indexList[0][inputIndex]) * stride;
						if ((firstIndex + 1) < sourceData.size())
						{
							Triangle* t = GetTriangle(triangleIndex);
							Vertex* v = t->GetVertex(index);
							v->SetTexCoord2D(sourceData[firstIndex + 0], sourceData[firstIndex + 1]);
						}
					}
				}
			}
		}
	}
}